#include "GlobalVariables.h"
#include "Constants.h"
#include <vector>
#include <string.h>

namespace CEngine
{

namespace GlobalVariables
{
    namespace {

        ///
        ///Variable structure and internal functions.
        ///

        union VariableData{
            CHAR* string;
            bool boolean;
            F32 singlePrecision;
            D64 doublePrecision;
            I64 number;
            U64 numberU;
        };

        struct Variable {
                CHAR* name;
                VariableData value;
                VariableData defaultValue;
                VariableType type;
        };

        bool
        findVariable(CHAR* name, std::vector<Variable>::iterator& indice);

        bool
        clampNumbers(CHAR* variableName, VariableType variableType, VariableType inputType, U8 code, void* input);

        void
        errorMessage(U8 errorCode, CHAR* variableName, VariableType variableType = BOOL, VariableType secondType = BOOL);
    }

    ///
    ///Internal strings and container.
    ///

    const static CHAR* types[] = {"bool", "string", "D64", "I64", "U64", "I32", "U32", "I16", "U16", "I8", "U8"};
    const static CHAR* null = "(NULL)";

    static std::vector<Variable> variables;

    ///
    ///Variable creation functions.
    ///

    void
    createVariable(CHAR* varName, VariableType type, bool defaultVariable) {
        if(type != BOOL) {
            errorMessage(2, varName, type, BOOL);
            return;
        }
        auto indice = variables.begin();
        if(findVariable(varName, indice)) {
            errorMessage(1, varName, type, indice->type);
            return;
        }

        Variable temp;
        temp.name = varName;
        temp.type = type;
        temp.value.boolean = defaultVariable;
        temp.defaultValue.boolean = defaultVariable;

        variables.push_back(temp);
    }

    void
    createVariable(CHAR* varName, VariableType type, CHAR* defaultVariable) {
        if(type != STRING) {
            errorMessage(2, varName, type, STRING);
            return;
        }
        auto indice = variables.begin();
        if(findVariable(varName, indice)) {
            errorMessage(1, varName, type, indice->type);
            return;
        }
        if(defaultVariable == NULL) {
            defaultVariable = (CHAR*)null;
        }

        Variable temp;
        temp.name = varName;
        temp.type = type;
        temp.value.string = defaultVariable;
        temp.defaultValue.string = defaultVariable;

        variables.push_back(temp);
    }

    void
    createVariable(CHAR* varName, VariableType type, I64 defaultVariable) {
        if(!clampNumbers(varName, type, N64, 1, (void*)&defaultVariable)) {
            errorMessage(2, varName, type, N64);
            return;
        }

        auto indice = variables.begin();
        if(findVariable(varName, indice)) {
            errorMessage(1, varName, type, indice->type);
            return;
        }

        Variable temp;
        temp.name = varName;
        temp.type = type;
        temp.value.number = defaultVariable;
        temp.defaultValue.number = defaultVariable;

        variables.push_back(temp);
    }

    void
    createVariable(CHAR* varName, VariableType type, U64 defaultVariable) {
        if(!clampNumbers(varName, type, N64U, 1, (void*)&defaultVariable)) {
            errorMessage(2, varName, type, N64U);
            return;
        }

        auto indice = variables.begin();
        if(findVariable(varName, indice)) {
            errorMessage(1, varName, type, indice->type);
            return;
        }

        Variable temp;
        temp.name = varName;
        temp.type = type;
        temp.value.numberU = defaultVariable;
        temp.defaultValue.numberU = defaultVariable;

        variables.push_back(temp);
    }

    ///
    ///Variable reading functions.
    ///

    void
    get(CHAR* varName, bool& value) {
        auto indice = variables.begin();

        if(!findVariable(varName, indice)) {
            errorMessage(3, varName);
            return;
        }

        if(indice->type != BOOL) {
            errorMessage(5, varName, indice->type, BOOL);
            return;
        }

        value = indice->value.boolean;
    }

    void
    get(CHAR* varName, CHAR*& value) {
        auto indice = variables.begin();

        if(!findVariable(varName, indice)) {
            errorMessage(3, varName);
            return;
        }

        if(indice->type != STRING) {
            errorMessage(5, varName, indice->type, STRING);
            return;
        }

        value = indice->value.string;
    }

    void
    get(CHAR* varName, I64& value) {
        auto indice = variables.begin();

        if(!findVariable(varName, indice)) {
            errorMessage(3, varName);
            return;
        }

        if(indice->type == N64U) {
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) copied to %s! Range limited to 0 - 9,223,372,036,854,775,807.", varName, types[(U8)indice->type], types[(U8)N64]);
            value = indice->value.number;
            if(value > 9223372036854775807) {
                value = 9223372036854775807;
            }
            return;
        }
        else if(indice->type != N64) {
            errorMessage(5, varName, indice->type, N64);
            return;
        }

        value = indice->value.number;
    }

    void
    get(CHAR* varName, U64& value) {
        auto indice = variables.begin();

        if(!findVariable(varName, indice)) {
            errorMessage(3, varName);
            return;
        }

        if(indice->type == N64) {
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) copied to %s! Range limited to 0 - 9,223,372,036,854,775,807.", varName, types[(U8)indice->type], types[(U8)N64]);
            value = indice->value.number;
            if(value < 0) {
                value = 0;
            }
            return;
        }
        else if(indice->type != N64U) {
            errorMessage(5, varName, indice->type, N64U);
            return;
        }

        value = indice->value.numberU;
    }

    ///
    ///Variable writing functions.
    ///

    void
    set(CHAR* varName, bool input) {
        auto indice = variables.begin();

        if(!findVariable(varName, indice)) {
            errorMessage(4, varName);
            return;
        }

        if(indice->type != BOOL) {
            errorMessage(6, varName, indice->type, BOOL);
            return;
        }

        indice->value.boolean = input;
    }

    void
    set(CHAR* varName, CHAR* input) {
        auto indice = variables.begin();

        if(!findVariable(varName, indice)) {
            errorMessage(4, varName);
            return;
        }

        if(indice->type != STRING) {
            errorMessage(6, varName, indice->type, STRING);
            return;
        }

        if(input == NULL) {
            input = (CHAR*)null;
        }

        indice->value.string = input;
    }

    void
    set(CHAR* varName, I64 input) {
        auto indice = variables.begin();

        if(!findVariable(varName, indice)) {
            errorMessage(4, varName);
            return;
        }

        switch(indice->type) {
        case N64:
            indice->value.number = input;
            break;
        case N64U:
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) set with %s! Range limited to 0 - 9,223,372,036,854,775,807.", varName, types[(U8)indice->type], types[(U8)N64]);
            if(input < 0) {
                input = 0;
            }
            indice->value.numberU = (U64)input;
            break;
        case N32:
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) set with %s! Range limited to -2,147,483,648 - 2,147,483,647.", varName, types[(U8)indice->type], types[(U8)N64]);
            if(input < -2147483648) {
                input = -2147483648;
            }
            else if(input > 2147483647) {
                input = 2147483647;
            }
            indice->value.number = input;
            break;
        case N32U:
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) set with %s! Range limited to 0 - 4,294,967,295.", varName, types[(U8)indice->type], types[(U8)N64]);
            if(input < 0) {
                input = 0;
            }
            else if(input > 4294967295) {
                input = 4294967295;
            }
            indice->value.numberU = (U32)input;
            break;
        case N16:
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) set with %s! Range limited to -32,768 - 32,767.", varName, types[(U8)indice->type], types[(U8)N64]);
            if(input < -32768) {
                input = -32768;
            }
            else if(input > 32767) {
                input = 32767;
            }
            indice->value.number = input;
            break;
        case N16U:
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) set with %s! Range limited to 0 - 65,535.", varName, types[(U8)indice->type], types[(U8)N64]);
            if(input < 0) {
                input = 0;
            }
            else if(input > 65535) {
                input = 65535;
            }
            indice->value.numberU = (U16)input;
            break;
        case N8:
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) set with %s! Range limited to -128 - 127.", varName, types[(U8)indice->type], types[(U8)N64]);
            if(input < -128) {
                input = -128;
            }
            else if(input > 127) {
                input = 127;
            }
            indice->value.number = input;
            break;
        case N8U:
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) set with %s! Range limited to 0 - 65,535.", varName, types[(U8)indice->type], types[(U8)N64]);
            if(input < 0) {
                input = 0;
            }
            else if(input > 255) {
                input = 255;
            }
            indice->value.numberU = (U8)input;
            break;
        default:
            defaultLog.log(Logging::SETTINGS, LOG_ERROR, "Variable \"%s\" (%s) set with %s!", varName, types[(U8)indice->type]), types[(U8)N64];
            return;
        }
    }

    void
    set(CHAR* varName, U64 input) {
        auto indice = variables.begin();

        if(!findVariable(varName, indice)) {
            errorMessage(4, varName);
            return;
        }

        switch(indice->type) {
        case N64:
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) set with %s! Range limited to 0 - 9,223,372,036,854,775,807.", varName, types[(U8)indice->type], types[(U8)N64U]);
            if(input > 9223372036854775807) {
                input = 9223372036854775807;
            }
            indice->value.number = (I64)input;
            break;
        case N64U:
            indice->value.numberU = input;
            break;
        case N32:
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) set with %s! Range limited to 0 - 2,147,483,647.", varName, types[(U8)indice->type], types[(U8)N64U]);
            if(input > 2147483647) {
                input = 2147483647;
            }
            indice->value.number = (I32)input;
            break;
        case N32U:
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) set with %s! Range limited to 0 - 4,294,967,295.", varName, types[(U8)indice->type], types[(U8)N64U]);
            if(input > 4294967295) {
                input = 4294967295;
            }
            indice->value.numberU = input;
            break;
        case N16:
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) set with %s! Range limited to 0 - 32,767.", varName, types[(U8)indice->type], types[(U8)N64U]);
            if(input > 32767) {
                input = 32767;
            }
            indice->value.number = (I16)input;
            break;
        case N16U:
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) set with %s! Range limited to 0 - 65,535.", varName, types[(U8)indice->type], types[(U8)N64U]);
            if(input > 65535) {
                input = 65535;
            }
            indice->value.numberU = input;
            break;
        case N8:
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) set with %s! Range limited to 0 - 127.", varName, types[(U8)indice->type], types[(U8)N64U]);
            if(input > 127) {
                input = 127;
            }
            indice->value.number = (I8)input;
            break;
        case N8U:
            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) set with %s! Range limited to 0 - 65,535.", varName, types[(U8)indice->type], types[(U8)N64U]);
            if(input > 65535) {
                input = 65535;
            }
            indice->value.numberU = input;
            break;
        default:
            defaultLog.log(Logging::SETTINGS, LOG_ERROR, "Variable \"%s\" (%s) set with %s!", varName, types[(U8)indice->type]), types[(U8)N64U];
            return;
        }
    }

    namespace {
        bool
        findVariable(CHAR* name, std::vector<Variable>::iterator& indice) {
            bool variableExists = false;

            for (auto i = variables.begin(); i != variables.end(); ++i) {
                if(!strcmp(i->name, name)) {
                    variableExists = true;
                    indice = i;
                    break;
                }
            }

            if(!variableExists) {
                return false;
            }
            return true;
        }

        bool
        clampNumbers(CHAR* varName, VariableType type, VariableType inputType, U8 code, void* input) {
            CHAR* mode;
            if(code == 1) {
                mode = "initialized";
            }
            else if(code == 2) {
                mode = "set";
            }

            if(type == STRING || type == BOOL || inputType == STRING || inputType == BOOL) {
                defaultLog.log(Logging::SETTINGS, LOG_ERROR, "Variable \"%s\" (%s) %s with %s!\n", varName, types[(U8)type], mode, types[(U8)inputType]);
                return false;
            }

            if(type == inputType) {
                return true;
            }

            U64 positiveRange = 0;
            I64 negativeRange = 0;

            if(type == N64U) {
                positiveRange = 18446744073709551615;
            }
            else if(type == N32U) {
                positiveRange = 4294967295;
            }
            else if(type == N16U) {
                positiveRange = 65535;
            }
            else if(type == N8U) {
                positiveRange = 255;
            }
            else {
                if(type == N64) {
                    positiveRange = 9223372036854775807;
                    negativeRange = -9223372036854775808;
                }
                else if(type == N32) {
                    positiveRange = 2147483647;
                    negativeRange = -2147483648;
                }
                else if(type == N16) {
                    positiveRange = 32767;
                    negativeRange = -32768;
                }
                else {
                    positiveRange = 127;
                    negativeRange = -128;
                }
            }

            I64 number;
            U64 numberU;

            switch(inputType) {
            case N64:
                if(type == N64U) {
                    positiveRange = 9223372036854775807;
                }

                number = *(I64*)input;
                if(number < negativeRange) {
                    number = negativeRange;
                }
                if(number > positiveRange) {
                    number = positiveRange;
                }
                *(I64*)input = number;
                break;
            case N64U:
                numberU = *(U64*)input;
                if(numberU < negativeRange) {
                    numberU = negativeRange;
                }
                if(numberU > positiveRange) {
                    numberU = positiveRange;
                }
                *(U64*)input = numberU;
                break;
            case N32:
                if(type == N64) {
                    positiveRange = 2147483647;
                    negativeRange = -2147483648;
                }
                else if(type == N64U) {
                    positiveRange = 2147483647;
                }
                else if(type == N32U) {
                    positiveRange = 2147483647;
                }

                number = *(I32*)input;
                if(number < negativeRange) {
                    number = negativeRange;
                }
                if(number > positiveRange) {
                    number = positiveRange;
                }
                *(I32*)input = number;
                break;
            case N32U:
                if(type == N64) {
                    positiveRange = 4294967295;
                    negativeRange = 0;
                }
                else if(type == N64U) {
                    positiveRange = 4294967295;
                }

                numberU = *(U32*)input;
                if(numberU < negativeRange) {
                    numberU = negativeRange;
                }
                if(numberU > positiveRange) {
                    numberU = positiveRange;
                }
                *(U32*)input = numberU;
                break;
            case N16:
                if(type == N64) {
                    positiveRange = 32767;
                    negativeRange = -32768;
                }
                else if(type == N64U) {
                    positiveRange = 32767;
                }
                else if(type == N32) {
                    positiveRange = 32767;
                    negativeRange = -32768;
                }
                else if(type == N32U) {
                    positiveRange = 32767;
                }
                else if(type == N16U) {
                    positiveRange = 32767;
                }

                number = *(I16*)input;
                if(number < negativeRange) {
                    number = negativeRange;
                }
                if(number > positiveRange) {
                    number = positiveRange;
                }
                *(I16*)input = number;
                break;
            case N16U:
                if(type == N64) {
                    positiveRange = 65535;
                    negativeRange = 0;
                }
                else if(type == N64U) {
                    positiveRange = 65535;
                }
                else if(type == N32) {
                    positiveRange = 65535;
                    negativeRange = 0;
                }
                else if(type == N32U) {
                    positiveRange = 65535;
                }

                numberU = *(U16*)input;
                if(numberU < negativeRange) {
                    numberU = negativeRange;
                }
                if(numberU > positiveRange) {
                    numberU = positiveRange;
                }
                *(U16*)input = numberU;
                break;
            case N8:
                if(type == N64) {
                    positiveRange = 127;
                    negativeRange = -128;
                }
                else if(type == N64U) {
                    positiveRange = 127;
                }
                else if(type == N32) {
                    positiveRange = 127;
                    negativeRange = -128;
                }
                else if(type == N32U) {
                    positiveRange = 127;
                }
                else if(type == N16) {
                    positiveRange = 127;
                    negativeRange = -128;
                }
                else if(type == N16U) {
                    positiveRange = 127;
                }
                else if(type == N8U) {
                    positiveRange = 127;
                }

                number = *(I8*)input;
                if(number < negativeRange) {
                    number = negativeRange;
                }
                if(number > positiveRange) {
                    number = positiveRange;
                }
                *(I8*)input = number;
                break;
            case N8U:
                if(type == N64) {
                    positiveRange = 255;
                    negativeRange = 0;
                }
                else if(type == N64U) {
                    positiveRange = 255;
                }
                else if(type == N32) {
                    positiveRange = 255;
                    negativeRange = 0;
                }
                else if(type == N32U) {
                    positiveRange = 255;
                }
                else if(type == N16) {
                    positiveRange = 255;
                    negativeRange = 0;
                }
                else if(type == N16U) {
                    positiveRange = 255;
                }

                numberU = *(U8*)input;
                if(numberU < negativeRange) {
                    numberU = negativeRange;
                }
                if(numberU > positiveRange) {
                    numberU = positiveRange;
                }
                *(U8*)input = numberU;
                break;
            }

            defaultLog.log(Logging::SETTINGS, LOG_WARNING, "Variable \"%s\" (%s) %s with %s! Range limited to %lld - %llu.\n", varName, types[(U8)type], mode, types[(U8)inputType], negativeRange, positiveRange);
            return true;
        }

        void
        errorMessage(U8 code, CHAR* varName, VariableType varType, VariableType secType) {
            switch(code) {
            case 1:
                defaultLog.log(Logging::SETTINGS, LOG_ERROR, "Variable \"%s\" (%s) already exists as a %s!\n", varName, types[(U8)varType], types[(U8)secType]);
                break;
            case 2:
                defaultLog.log(Logging::SETTINGS, LOG_ERROR, "Variable \"%s\" (%s) initialized with %s!\n", varName, types[(U8)varType], types[(U8)secType]);
                break;
            case 3:
                defaultLog.log(Logging::SETTINGS, LOG_ERROR, "Could not read variable \"%s\". Does not exist.\n", varName);
                break;
            case 4:
                defaultLog.log(Logging::SETTINGS, LOG_ERROR, "Could not write variable \"%s\". Does not exist.\n", varName);
                break;
            case 5:
                defaultLog.log(Logging::SETTINGS, LOG_ERROR, "Could not read variable \"%s\" (%s). Is not a %s.\n", varName, types[(U8)varType], types[(U8)secType]);
                break;
            case 6:
                defaultLog.log(Logging::SETTINGS, LOG_ERROR, "Could not write variable \"%s\" (%s). Is not a %s.\n", varName, types[(U8)varType], types[(U8)secType]);
                break;
            }
        }
    }
}

}
